/**
 * Copyright (C) 2017 Fuzhou Rockchip Electronics Co., Ltd
 * author: hertz.wang hertz.wong@rock-chips.com
 *
 * This software is licensed under the terms of the GNU General Public
 * License version 2, as published by the Free Software Foundation, and
 * may be copied, distributed, and modified under those terms.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 */

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <poll.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/time.h>

#include "scale_encode_ts_handler.h"
#include "sfserver_interface.h"

int scale_encode_copy_extradata(FFContext& ff_ctx, MPPH264Encoder& mpp_h264_enc) {
  void* extra_data = nullptr;
  size_t extra_data_size = 0;
  mpp_h264_enc.GetExtraData(extra_data, extra_data_size);
  assert(extra_data);
  if (extra_data) {
    AVCodecContext* avctx = ff_ctx.GetAVCodecContext();
    assert(avctx);
    assert(extra_data_size > 0);
    if (extra_data_size != avctx->extradata_size) {
      if (avctx->extradata != NULL)
        av_free(avctx->extradata);
      avctx->extradata = (uint8_t*)av_malloc(extra_data_size);
      if (!avctx->extradata) {
        PRINTF_NO_MEMORY;
        return -1;
      }
      avctx->extradata_size = extra_data_size;
    }
    memcpy(avctx->extradata, extra_data, extra_data_size);
    avctx->codec_id = AV_CODEC_ID_H264;
    return 0;
  }
  return -1;
}


ScaleEncodeTSHandler::ScaleEncodeTSHandler(MediaConfig& config)
    : oc(nullptr), state_(RUNNING), scale_config_(config), time_val_{0, 0} 
{
	snprintf(format, sizeof(format), "mpegts");
	strMediaBuf = (char *)malloc(200000 + 128);
}


ScaleEncodeTSHandler::~ScaleEncodeTSHandler()
{
	state_ = EXIT;
	if(nullptr != strMediaBuf)
		free(strMediaBuf);
    DeInit();
}

int ScaleEncodeTSHandler::get_spspps(AVFormatContext* oc, char *spspps, int *len){
  int i,j;
  int tmplen;
  char tmp[16];
  for (i = 0; i < oc->nb_streams; i++) {
    AVStream* st = oc->streams[i];
    AVCodecContext* codec = st ? st->codec : nullptr;
    if (codec && codec->codec_id == AV_CODEC_ID_H264) 
	{
		spspps[0] = 0;
		memcpy(&spspps[1],codec->extradata,codec->extradata_size);
		tmplen = codec->extradata_size + 1;
		for(j=0;j<codec->extradata_size;j++)
			printf("%02x ",codec->extradata[j]);
		printf("\n");
		
		for(j=0;j<(tmplen-4);j++)
		{
		  if((spspps[j] == 0) && (spspps[j+1] == 0) && (spspps[j+2] == 1) && (spspps[j+3] == 0x68) )
			  break;
		}
		if(j != tmplen)
		{
		  tmp[0] = 0;
		  memcpy(&tmp[1],&spspps[j],tmplen-j);
		  memcpy(&spspps[j],tmp,tmplen-j+1);
		}
		tmplen += 1;
		*len = tmplen;
		for(j=0;j<tmplen;j++)
		  printf("%02x ",spspps[j]);
		printf("\n");
  
      /*struct timeval time = {0, 0};
      gettimeofday(&time, nullptr);
      if (!PipeWriteSpsPps(codec->extradata, codec->extradata_size, time, 3))
        return 0;*/
    }
  }
  
  return 0;
}

int ScaleEncodeTSHandler::Init(MediaConfig src_config)
{
	int ret = 0;
	AVCodecContext* output_codec_context = NULL;
	AVStream* output_stream = NULL;	
	FFContext* ff_ctx;
	AVStream* video_stream;

    if (ff_ctx_.InitConfig(src_config.video_config, false))
        return -1;
    if (ScaleEncodeHandler<MPPH264Encoder>::Init(
            src_config, 3, 2, scale_config_.video_config.width,
            scale_config_.video_config.height))
        return -1;
    if (scale_encode_copy_extradata(ff_ctx_, *encoder_))
        return -1;
    encoder_->SetHelpContext(&ff_ctx_);

	// sunflower get the spspps info.
	ff_ctx = static_cast<FFContext*>(encoder_->GetHelpContext());
	video_stream = ff_ctx->GetAVStream();

	/* allocate the output media context */
	ret = avformat_alloc_output_context2(&oc, NULL, format, NULL);
	if (!oc) {
		fprintf(stderr, "avformat_alloc_output_context2 failed for %s, ret: %d\n", format, ret);
		return -1;
	}

	output_stream = avformat_new_stream(oc, video_stream->codec->codec);
	if (!output_stream) {
		printf("Call avformat_new_stream function failed\n");
		return -1;
	}
	output_stream->time_base = video_stream->codec->time_base;
	if (0 > avcodec_copy_context(output_stream->codec, video_stream->codec)) {
		fprintf(stderr, "Failed to copy context from input to output stream codec context\n");
		return -1;
	}
	output_stream->codec->codec_tag = 0;
	output_codec_context = output_stream->codec;

	if (oc->oformat->flags & AVFMT_GLOBALHEADER) {
		output_codec_context->flags |= CODEC_FLAG_GLOBAL_HEADER;
	}	  
	if (0 != get_spspps(oc, spspps, &spspps_len)) {
		return -1;
	}
		
    return 0;
}

void ScaleEncodeTSHandler::DeInit()
{
	if (oc) {
	  for (unsigned int i = 0; i < oc->nb_streams; i++) {
		AVStream* st = oc->streams[i];
		if (st) {
		  avcodec_close(st->codec);
		}
	  }
	  if (!(oc->oformat->flags & AVFMT_NOFILE)) {
		/* Close the output file. */
		avio_closep(&oc->pb);
	  }
	  avformat_free_context(oc);
	  oc = nullptr;
	}

    ScaleEncodeHandler<MPPH264Encoder>::DeInit();
    {
        std::lock_guard<std::mutex> _lk(mtx_);		
    }
}

void ScaleEncodeTSHandler::GetSrcConfig(const MediaConfig& src_config,
                                        int& src_w,
                                        int& src_h,
                                        PixelFormat& src_fmt)
{
    src_w = src_config.video_config.width;
    src_h = src_config.video_config.height;
    src_fmt = src_config.video_config.fmt;
}

int ScaleEncodeTSHandler::PrepareBuffers(MediaConfig& src_config,
                                         const int dst_numerator,
                                         const int dst_denominator,
                                         int& dst_w,
                                         int& dst_h)
{
    int ret = ScaleEncodeHandler<MPPH264Encoder>::PrepareBuffers(
                  src_config, dst_numerator, dst_denominator, dst_w, dst_h);
    if (ret)
        return ret;
    VideoConfig& vconfig = src_config.video_config;
    vconfig = scale_config_.video_config;
    vconfig.fmt = kCacheFmt;
    vconfig.width = dst_w;
    vconfig.height = dst_h;
    return 0;
}

void ScaleEncodeTSHandler::Work()
{
	int key, flags;
	SF_MEDIA_FRAME_HEAD_S stFrameHead;
	static unsigned char u8FrameIndex = 0;
	int tmplen;
	uint8_t *h264data;

	if(strMediaBuf == nullptr)
		return;

    Buffer src(yuv_data_);
    Buffer dst(dst_data_);
    int ret = encoder_->EncodeOneFrame(&src, &dst, nullptr);
    if (!ret) {
        if (dst.GetUserFlag() & MPP_PACKET_FLAG_INTRA)
            flags |= AV_PKT_FLAG_KEY;
        // rk guarantee that return only one slice, and start with 00 00 01
        flags |= AV_PKT_FLAG_ONE_NAL;

		key = (flags &= AV_PKT_FLAG_KEY) ? 1 : 0;
		  
		memset(&stFrameHead,0x00,sizeof(stFrameHead));
		stFrameHead.mediaType = MEDIA_TYPE_VIDEO;
		stFrameHead.videoFrameHead.iFrameDataLen = 0;
		stFrameHead.videoFrameHead.iCode = 4;//pMediaSysInfo->mediaSysParam.mediaAttr[iMediaId].videoAttr.encType;	
		stFrameHead.videoFrameHead.iFramerate = 25;//pMediaSysInfo->mediaSysParam.mediaAttr[iMediaId].videoAttr.iFrameRate;
		gettimeofday(&stFrameHead.timeStamp,NULL);
		//stFrameHead.videoFrameHead.iFrameDataLen = pkt->av_pkt.size;
		tmplen = dst.GetValidDataSize();
		h264data = static_cast<uint8_t*>(dst.GetVirAddr());
		if(key == 1)
		{
			stFrameHead.videoFrameHead.iIsKeyFrame = 1;
			u8FrameIndex = 0;
		}
		else
		{
			stFrameHead.videoFrameHead.iIsKeyFrame = 0;
		}
		stFrameHead.videoFrameHead.u8FrameIndex = u8FrameIndex;
		u8FrameIndex++;
		if( tmplen > 200000 )
		{
			return;
		}	

		if(key == 1)
		{
			memcpy(strMediaBuf, spspps, spspps_len);
			*(char*)(strMediaBuf+spspps_len) = 0;
			memcpy(strMediaBuf + spspps_len + 1, h264data, tmplen);
			tmplen += (spspps_len+1);
		}
		else
		{
			*(char*)strMediaBuf = 0;
			memcpy(strMediaBuf + 1, h264data, tmplen);
			tmplen += 1;
		}
				
		stFrameHead.videoFrameHead.iFrameDataLen = tmplen;
		//printf("start SF_Write_Buf: %d\n",tmplen);
		SF_Write_Buf(strMediaBuf, tmplen, stFrameHead);
    }
}

void ScaleEncodeTSHandler::Process(int src_fd,
                                   const VideoConfig& src_config,
                                   const struct timeval& time)
{
	std::lock_guard<std::mutex> _lk(mtx_);
	if(state_ != ENTER)
		return;
    // AutoPrintInterval hh("ScaleEncodeTSHandler::Process");
    time_val_ = time;
    ScaleEncodeHandler<MPPH264Encoder>::Process(src_fd, src_config);
}

int ScaleEncodeTSHandler::StartTransferStream(char* uri,
                                              pthread_attr_t* global_attr)
{
    std::lock_guard<std::mutex> _lk(mtx_);

	if(state_ == ENTER)
		return -1;

	state_ = ENTER;
		
	return 0;
}

int ScaleEncodeTSHandler::StopTransferStream()
{
    std::lock_guard<std::mutex> _lk(mtx_);

	if(state_ == EXIT)
		return -1;	

	state_ = EXIT;

    return 0;
}
